#include "src/net/tcp_server.h"

#include <cassert>
#include <cstdio>

#include "src/base/fio66_logger.h"
#include "src/net/acceptor.h"
#include "src/net/event_loop.h"
#include "src/net/event_loop_thread_pool.h"
#include "src/net/sockets_opt.h"

namespace fio66::net {
TcpServer::TcpServer(EventLoop* loop, const InetAddress& listenAddr,
                     const std::string& nameArg, Option option)
    : loop_(CHECK_NOTNULL(loop)),
      ip_port_(listenAddr.ToIpPort()),
      name_(nameArg),
      acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)),
      thread_pool_(new EventLoopThreadPool(loop, name_)),
      connection_callback_(DefaultConnectionCallback),
      message_callback_(DefaultMessageCallback),
      next_connId_(1) {
  acceptor_->SetNewConnectionCallback(
      std::bind(&TcpServer::NewConnection, this, _1, _2));
}

TcpServer::~TcpServer() {
  loop_->AssertInLoopThread();
  LOGD << "TcpServer::~TcpServer [" << name_ << "] destructing";

  for (auto& item : connections_) {
    TcpConnectionPtr conn(item.second);
    item.second.reset();
    conn->GetLoop()->RunInLoop(
        std::bind(&TcpConnection::ConnectDestroyed, conn));
  }
}

void TcpServer::SetThreadNum(int numThreads) {
  assert(0 <= numThreads);
  thread_pool_->SetThreadNum(numThreads);
}

void TcpServer::Start() {
  started_.store(1);
  if (started_) {
    thread_pool_->Start(thread_init_callback_);

    assert(!acceptor_->Listening());
    loop_->RunInLoop(std::bind(&Acceptor::Listen, get_pointer(acceptor_)));
  }
}

void TcpServer::NewConnection(int sockfd, const InetAddress& peerAddr) {
  loop_->AssertInLoopThread();
  EventLoop* ioLoop = thread_pool_->GetNextLoop();
  char buf[64];
  snprintf(buf, sizeof buf, "-%s#%d", ip_port_.c_str(), next_connId_);
  ++next_connId_;
  std::string connName = name_ + buf;

  LOGI << "TcpServer::NewConnection [" << name_ << "] - new connection ["
       << connName << "] from " << peerAddr.ToIpPort();
  InetAddress localAddr(sockets::GetLocalAddr(sockfd));
  // FIXME poll with zero timeout to double confirm the new connection
  // FIXME use make_shared if necessary
  TcpConnectionPtr conn(
      new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr));
  connections_[connName] = conn;
  conn->SetConnectionCallback(connection_callback_);
  conn->SetMessageCallback(message_callback_);
  conn->SetWriteCompleteCallback(write_complete_callback_);
  conn->SetCloseCallback(
      std::bind(&TcpServer::RemoveConnection, this, _1));  // FIXME: unsafe
  ioLoop->RunInLoop(std::bind(&TcpConnection::ConnectEstablished, conn));
}

void TcpServer::RemoveConnection(const TcpConnectionPtr& conn) {
  // FIXME: unsafe
  loop_->RunInLoop(std::bind(&TcpServer::RemoveConnectionInLoop, this, conn));
}

void TcpServer::RemoveConnectionInLoop(const TcpConnectionPtr& conn) {
  loop_->AssertInLoopThread();
  LOGI << "TcpServer::RemoveConnectionInLoop [" << name_ << "] - connection "
       << conn->Name();
  size_t n = connections_.erase(conn->Name());
  (void)n;
  assert(n == 1);
  EventLoop* ioLoop = conn->GetLoop();
  ioLoop->QueueInLoop(std::bind(&TcpConnection::ConnectDestroyed, conn));
}

}  // namespace fio66::net
